package conf

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	_ "github.com/go-sql-driver/mysql"
	orm_mysql "gorm.io/driver/mysql"
	"gorm.io/gorm"
)

//

func DefaultConfig() *Config {
	return &Config{
		MySQL: newDefaultMySQL(),
		Http:  newDefaultHttp(),
		Grpc:  NewDefaultGrpc(),
	}
}

// 程序的配置对象
type Config struct {
	// [mysql]
	MySQL *MySQL `json:"mysql" toml:"mysql"`
	// [http]
	Http *Http `json:"http" toml:"http"`
	// [grpc]
	Grpc *Grpc `json:"grpc" toml:"grpc"`
}

type MySQL struct {
	Host     string `json:"host" toml:"host" env:"MYSQL_HOST"`
	Port     int    `json:"port" toml:"port" env:"MYSQL_PORT"`
	DB       string `json:"db" toml:"db" env:"MYSQL_DB"`
	Username string `json:"username" toml:"username" env:"MYSQL_USERNAME"`
	Password string `json:"password" toml:"password" env:"MYSQL_PASSWORD"`
	// 高级参数
	MaxOpenConn int `toml:"max_open_conn" env:"MYSQL_MAX_OPEN_CONN"`
	MaxIdleConn int `toml:"max_idle_conn" env:"MYSQL_MAX_IDLE_CONN"`
	MaxLifeTime int `toml:"max_life_time" env:"MYSQL_MAX_LIFE_TIME"`
	MaxIdleTime int `toml:"max_idle_time" env:"MYSQL_MAX_IDLE_TIME"`

	// 面临并发安全
	lock sync.Mutex
	db   *gorm.DB
}

// go mod tidy
// gorm官方文档 https://gorm.io/docs/connecting_to_the_database.html
// 这里的*gorm.DB 是一个单例实例
func (m *MySQL) ORM() *gorm.DB {
	m.lock.Lock()
	defer m.lock.Unlock()
	if m.db == nil {
		// 初始化DB
		// 1.1 获取sql.DB
		p, err := m.GetConnPool()
		if err != nil {
			panic(err)
		}
		// 1.2 使用pool 初始化orm db对象
		m.db, err = gorm.Open(orm_mysql.New(
			orm_mysql.Config{
				Conn: p,
			}), &gorm.Config{
			// 执行任何SQL 时都创建并缓存预编译语句,可以提高后续的调用速度
			PrepareStmt: true,
			// 对于写操作(创建, 更新, 删除), 为了确保数据的完整性,GOR 会将它们封装在事物内运行.
			// 但是这会降低性能, 如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约30%+ 性能
			SkipDefaultTransaction: true,
			// 更有效地插入大量记录, 请将一个slice 传递给Create方法
			//CreateBatchSize: 200,
		})
		if err != nil {
			panic(err)
		}
	}
	return m.db

}

func (m *MySQL) Close() error {
	if m.db == nil {
		return nil
	}
	// 没有提供Clos方法
	// 没法实现数据库的优雅关闭
	return nil
}

// 获取连接池对象,
// 驱动: _ "github.com/go-sql-driver/mysql"
// 适配标准库: sql.DB, 连接池(在驱动层之上)

func (m *MySQL) GetConnPool() (*sql.DB, error) {
	var err error
	// multiStatements 让db可以执行多个语句 select; insert;
	// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
	//dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&multiStatements=true",
		m.Username, m.Password, m.Host, m.Port, m.DB)
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, fmt.Errorf("connect to mysql<%s> error, %s", dsn, err.Error())
	}
	// 对连接池进行设置
	db.SetMaxOpenConns(m.MaxOpenConn)
	db.SetMaxIdleConns(m.MaxIdleConn)
	if m.MaxLifeTime != 0 {
		db.SetConnMaxLifetime(time.Second * time.Duration(m.MaxLifeTime))
	}
	if m.MaxIdleConn != 0 {
		db.SetConnMaxIdleTime(time.Second * time.Duration(m.MaxIdleTime))
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		return nil, fmt.Errorf("ping mysql<%s> error, %s", dsn, err.Error())
	}
	return db, nil
}

func newDefaultMySQL() *MySQL {
	return &MySQL{
		Host:     "127.0.0.1",
		Port:     3306,
		DB:       "vblog",
		Username: "vblog",
		Password: "vblog",
	}
}
func (c *Config) String() string {
	d, _ := json.MarshalIndent(c, "", " ")
	return string(d)
}

func newDefaultHttp() *Http {
	return &Http{
		Host: "0.0.0.0",
		Port: 8800,
	}
}

type Http struct {
	Host string `json:"host" toml:"host" env:"HTTP_HOST"`
	Port int    `json:"port" toml:"port" env:"HTTP_PORT"`
}

func (h Http) Address() string {
	return fmt.Sprintf("%s:%d", h.Host, h.Port)
}

func NewDefaultGrpc() *Grpc {
	return &Grpc{
		Host: "0.0.0.0",
		Port: 18800,
	}
}

type Grpc struct {
	Host string `json:"host" toml:"host" env:"GRPC_HOST"`
	Port int    `json:"port" toml:"port" env:"GRPC_PORT"`
}

func (h Grpc) Address() string {
	return fmt.Sprintf("%s:%d", h.Host, h.Port)
}
